﻿using System;
using System.Collections.ObjectModel;
using ParserGenerator.Collections;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.Output;
using ParserGenerator.ParserConstruction;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.CodeGeneration
{
    [Serializable]
    public sealed class CSharpParserGenerator : CSharpSyntacticActionWriter
    {
        public ParserAutomatonTable Table { get; }
        public ParserAutomaton Automaton => Table.Automaton;
        public CSharpScannerGenerator ScannerGenerator { get; }
        public bool UseEmbeddedTokenizer => true;

        public CSharpParserGenerator(GrammarDeclaration grammar, ParserAutomatonTable table)
            : base(grammar)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            if (table == null)
                throw new ArgumentNullException(nameof(table));
            Table = table;
            ScannerGenerator = new CSharpScannerGenerator(grammar, table.ScannerTable);
        }

        public void Generate()
        {
            Generate(new IndentedWriter());
        }

        public void Generate(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateNamespace(writer);
        }

        public void GenerateNamespace(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string parserNamespace = Grammar.GetOptionValue("pg.parser.namespace", "ParserGenerator.SyntacticAnalysis");
            writer.Write("namespace ");
            WriteDottedName(parserNamespace, writer);
            writer.WriteLine();
            writer.OpenCurlyBlock();
            GenerateUsings(writer);
            GenerateParserClass(writer);
            writer.CloseCurlyBlock();
        }

        public void GenerateUsings(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Expression v;
            if (Grammar.TryGetOptionValue("pg.parser.usings", out v))
            {
                Sequence<Expression> symbols;
                if (v.IsTuple)
                    symbols = v.AsTuple.Elements;
                else
                    symbols = v.AsList.Elements;
                foreach (Expression symbol in symbols)
                {
                    writer.Write("using ");
                    WriteExpression(symbol, writer);
                    writer.WriteLine(";");
                }
                writer.WriteLine();
            }
        }

        public void GenerateParserClass(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            bool isSerializable = Grammar.GetOptionValue("pg.parser.serializable", true);
            bool isInternal = Grammar.GetOptionValue("pg.parser.internal", false);
            bool isPublic = Grammar.GetOptionValue("pg.parser.public", true);
            bool isAbstract = Grammar.GetOptionValue("pg.parser.abstract", false);
            bool isSealed = Grammar.GetOptionValue("pg.parser.sealed", true);
            bool isStatic = Grammar.GetOptionValue("pg.parser.static", false);
            bool isPartial = Grammar.GetOptionValue("pg.parser.partial", true);
            Identifier parserClassName = Identifier.New(Grammar.GetOptionValue("pg.parser.classname", "Parser"));
            if (isSerializable)
            {
                writer.Write("[");
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.WriteLine("Serializable]");
            }
            writer.Write(isInternal ? "internal " : isPublic ? "public " : "");
            writer.Write(isAbstract ? "abstract " : isSealed ? "sealed " : isStatic ? "static " : "");
            writer.Write(isPartial ? "partial " : "");
            writer.Write("class ");
            WriteIdentifier(parserClassName, writer);
            writer.WriteLine();
            writer.OpenCurlyBlock();
            GenerateFields(writer);
            writer.WriteLine();
            GenerateProperties(writer);
            if (UseEmbeddedTokenizer)
            {
                writer.WriteLine();
                ScannerGenerator.GenerateTokenTags(writer);
            }
            writer.WriteLine();
            GenerateConstructors(writer);
            if (UseEmbeddedTokenizer)
            {
                if (isIndented)
                {
                    writer.WriteLine();
                    ScannerGenerator.GeneratePrescanNextTokenMethod(writer);
                    writer.WriteLine();
                    ScannerGenerator.GenerateCheckIndentationMethod(writer);
                }
                writer.WriteLine();
                ScannerGenerator.GenerateStartMethods(writer);
                writer.WriteLine();
                ScannerGenerator.GenerateSyntaxErrorMethods(writer);
            }
            writer.WriteLine();
            GenerateArrays(writer);
            if (UseEmbeddedTokenizer)
            {
                writer.WriteLine();
                ScannerGenerator.GenerateMapMethod(writer);
                writer.WriteLine();
                ScannerGenerator.GenerateMoveMethod(writer);
                writer.WriteLine();
                ScannerGenerator.GenerateIsLineTerminatorMethod(writer);
                if (isIndented)
                {
                    writer.WriteLine();
                    ScannerGenerator.GenerateIsNonLineTerminatorWhiteSpaceMethod(writer);
                }
                writer.WriteLine();
                ScannerGenerator.GenerateScanNextTokenMethod(writer);
                writer.WriteLine();
                ScannerGenerator.GenerateGetNextTokenMethod(writer);
                if (Automaton.Tokens.NamedTokens.ContainsKey("Identifier"))
                {
                    writer.WriteLine();
                    ScannerGenerator.GenerateMapIdentifierMethod(writer);
                    writer.WriteLine();
                    ScannerGenerator.GenerateIsKeywordMethod(writer);
                }
            }
            writer.WriteLine();
            GenerateActionMethod(writer);
            writer.WriteLine();
            GenerateGotoMethod(writer);
            writer.WriteLine();
            GenerateMakeErrorMethod(writer);
            writer.WriteLine();
            GenerateReduceMethod(writer);
            writer.WriteLine();
            GenerateParseGoalMethod(writer);
            writer.WriteLine();
            GenerateParseMethods(writer);
            writer.WriteLine();
            GenerateTestMethod(writer);
            writer.CloseCurlyBlock();
        }

        public void GenerateFields(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier nodeContextName = Identifier.New(Grammar.GetOptionValue("pg.parser.nodecontext.name", "_nodeContext"));
            Identifier statesName = Identifier.New(Grammar.GetOptionValue("pg.parser.states.name", "_states"));
            Identifier symbolsName = Identifier.New(Grammar.GetOptionValue("pg.parser.symbols.name", "_symbols"));
            Identifier valuesName = Identifier.New(Grammar.GetOptionValue("pg.parser.values.name", "_values"));
            Identifier contextsName = Identifier.New(Grammar.GetOptionValue("pg.parser.contexts.name", "_contexts"));
            Identifier sourceContextClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));
            string sourceContextNamespace = Grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.Input");
            Identifier arrayListClassName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.classname", "List"));
            string arrayListNamespace = Grammar.GetOptionValue("pg.arraylist.namespace", "System.Collections.Generic");
            if (UseEmbeddedTokenizer)
                ScannerGenerator.GenerateFields(writer);
            writer.Write("private ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(" ");
            WriteIdentifier(nodeContextName, writer);
            writer.WriteLine(";");
            writer.Write("private readonly ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.Write("<int> ");
            WriteIdentifier(statesName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.WriteLine("<int>();");
            writer.Write("private readonly ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.Write("<string> ");
            WriteIdentifier(symbolsName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.WriteLine("<string>();");
            writer.Write("private readonly ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.Write("<object> ");
            WriteIdentifier(valuesName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.WriteLine("<object>();");
            writer.Write("private readonly ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.Write("<");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write("> ");
            WriteIdentifier(contextsName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.Write("<");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.WriteLine(">();");
        }

        public void GenerateProperties(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (UseEmbeddedTokenizer)
            {
                ScannerGenerator.GenerateProperties(writer);
                return;
            }
            Identifier tokenizerName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenizer.name", "Tokenizer"));
            Identifier tokenizerClassName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenizer.classname", "Tokenizer"));
            string tokenizerNamespace = Grammar.GetOptionValue("pg.parser.tokenizer.namespace", "ParserGenerator.LexicalAnalysis");
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tokenizerNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tokenizerClassName, writer);
            writer.Write(" ");
            WriteIdentifier(tokenizerName, writer);
            writer.WriteLine(" { get; private set; }");
        }

        public void GenerateConstructors(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isAbstract = Grammar.GetOptionValue("pg.parser.abstract", false);
            Identifier parserClassName = Identifier.New(Grammar.GetOptionValue("pg.parser.classname", "Parser"));
            Identifier parserConstructorReaderName = Identifier.New(Grammar.GetOptionValue("pg.parser.constructor.reader.name", "reader"));
            Identifier parserConstructorTokenizerName = Identifier.New(Grammar.GetOptionValue("pg.parser.constructor.tokenizer.name", "tokenizer"));
            Identifier sourceReaderClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.classname", "SourceReader"));
            Identifier sourceReaderFromConsoleName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.fromconsole.name", "FromConsole"));
            string sourceReaderNamespace = Grammar.GetOptionValue("pg.sourcereader.namespace", "ParserGenerator.Input");
            Identifier tokenizerName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenizer.name", "Tokenizer"));
            Identifier tokenizerClassName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenizer.classname", "Tokenizer"));
            string tokenizerNamespace = Grammar.GetOptionValue("pg.parser.tokenizer.namespace", "ParserGenerator.LexicalAnalysis");
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            writer.Write(isAbstract ? "protected " : "public ");
            WriteIdentifier(parserClassName, writer);
            writer.WriteLine("()");
            writer.OpenBlock();
            writer.Write(": this(");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceReaderFromConsoleName, writer);
            writer.WriteLine("())");
            writer.CloseBlock();
            writer.OpenCurlyBlock();
            writer.CloseCurlyBlock();
            writer.WriteLine();
            writer.Write(isAbstract ? "protected " : "public ");
            WriteIdentifier(parserClassName, writer);
            writer.Write("(");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(" ");
            WriteIdentifier(parserConstructorReaderName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            if (UseEmbeddedTokenizer)
                ScannerGenerator.GenerateConstructorBody(writer);
            else
            {
                writer.Write("if (");
                WriteIdentifier(parserConstructorReaderName, writer);
                writer.WriteLine(" == null)");
                writer.OpenBlock();
                writer.Write("throw new ");
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.Write("ArgumentNullException(nameof(");
                WriteIdentifier(parserConstructorReaderName, writer);
                writer.WriteLine("));");
                writer.CloseBlock();
                WriteIdentifier(tokenizerName, writer);
                writer.Write(" = new ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(tokenizerNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(tokenizerClassName, writer);
                writer.Write("(");
                WriteIdentifier(readerName, writer);
                writer.WriteLine(");");
            }
            writer.CloseCurlyBlock();
            if (!UseEmbeddedTokenizer)
            {
                writer.WriteLine();
                writer.Write(isAbstract ? "protected " : "public ");
                WriteIdentifier(parserClassName, writer);
                writer.Write("(");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(tokenizerNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(tokenizerClassName, writer);
                writer.Write(" ");
                WriteIdentifier(parserConstructorTokenizerName, writer);
                writer.WriteLine(")");
                writer.OpenCurlyBlock();
                writer.Write("if (");
                WriteIdentifier(parserConstructorTokenizerName, writer);
                writer.WriteLine(" == null)");
                writer.OpenBlock();
                writer.Write("throw new ");
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.Write("ArgumentNullException(nameof(");
                WriteIdentifier(parserConstructorTokenizerName, writer);
                writer.WriteLine("));");
                writer.CloseBlock();
                WriteIdentifier(tokenizerName, writer);
                writer.Write(" = ");
                WriteIdentifier(parserConstructorTokenizerName, writer);
                writer.WriteLine(";");
                writer.CloseCurlyBlock();
            }
        }

        public void GenerateArrays(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (UseEmbeddedTokenizer)
            {
                ScannerGenerator.GenerateArrays(writer);
                writer.WriteLine();
            }
            GenerateStringArray("pg.parser.array.goals", "_goals", Table.GoalNameArray, writer);
            GenerateStringArray("pg.parser.array.nonterminals", "_nonterminals", Table.NonterminalNameArray, writer);
            GenerateStringArray("pg.parser.array.text", "_text", Table.TokenTextArray, writer);
            GenerateIntegerArray("pg.parser.array.goal", "_goal", Table.GoalArray, writer);
            GenerateIntegerArray("pg.parser.array.left", "_left", Table.LeftArray, writer);
            GenerateIntegerArray("pg.parser.array.length", "_length", Table.LengthArray, writer);
            GenerateIntegerArray("pg.parser.array.base", "_pbase", Table.ActionTable.BaseArray, writer);
            GenerateIntegerArray("pg.parser.array.check", "_pcheck", Table.ActionTable.CheckArray, writer);
            GenerateIntegerArray("pg.parser.array.value", "_pvalue", Table.ActionTable.ValueArray, writer, true);
        }

        public void GenerateStringArray(string key, string defaultName, ReadOnlyCollection<string> values, IndentedWriter writer)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateStringArray(key, defaultName, values, writer, false);
        }

        public void GenerateStringArray(string key, string defaultName, ReadOnlyCollection<string> values, IndentedWriter writer, bool isLast)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string access = Grammar.GetOptionValue(key + ".access", "private");
            Identifier name = Identifier.New(Grammar.GetOptionValue(key + ".name", defaultName));
            writer.Write(access);
            if (!access.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static readonly string[] ");
            WriteIdentifier(name, writer);
            writer.WriteLine(" =");
            writer.OpenCurlyBlock();
            WriteStringArrayElements(values, writer);
            writer.CloseBlock("};");
            if (!isLast)
                writer.WriteLine();
        }

        public void GenerateIntegerArray(string key, string defaultName, ReadOnlyCollection<int> values, IndentedWriter writer)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateIntegerArray(key, defaultName, values, writer, false);
        }

        public void GenerateIntegerArray(string key, string defaultName, ReadOnlyCollection<int> values, IndentedWriter writer, bool isLast)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string access = Grammar.GetOptionValue(key + ".access", "private");
            Identifier name = Identifier.New(Grammar.GetOptionValue(key + ".name", defaultName));
            writer.Write(access);
            if (!access.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static readonly int[] ");
            WriteIdentifier(name, writer);
            writer.WriteLine(" =");
            writer.OpenCurlyBlock();
            WriteIntegerArrayElements(values, writer);
            writer.CloseBlock("};");
            if (!isLast)
                writer.WriteLine();
        }

        public void GenerateActionMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string actionAccess = Grammar.GetOptionValue("pg.parser.action.access", "private");
            Identifier actionName = Identifier.New(Grammar.GetOptionValue("pg.parser.action.name", "Action"));
            Identifier stateName = Identifier.New(Grammar.GetOptionValue("pg.parser.state.name", "state"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.parser.c.name", "c"));
            Identifier baseArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.base.name", "_pbase"));
            Identifier checkArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.check.name", "_pcheck"));
            Identifier valueArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.value.name", "_pvalue"));
            writer.Write(actionAccess);
            if (!actionAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static int ");
            WriteIdentifier(actionName, writer);
            writer.Write("(int ");
            WriteIdentifier(stateName, writer);
            writer.Write(", int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(checkArrayName, writer);
            writer.Write("[");
            WriteIdentifier(baseArrayName, writer);
            writer.Write("[");
            WriteIdentifier(stateName, writer);
            writer.Write("] + ");
            WriteIdentifier(cName, writer);
            writer.Write("] == ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenBlock();
            writer.Write("return ");
            WriteIdentifier(valueArrayName, writer);
            writer.Write("[");
            WriteIdentifier(baseArrayName, writer);
            writer.Write("[");
            WriteIdentifier(stateName, writer);
            writer.Write("] + ");
            WriteIdentifier(cName, writer);
            writer.WriteLine("];");
            writer.CloseBlock();
            writer.WriteLine("else");
            writer.OpenBlock();
            writer.WriteLine("return 0;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateGotoMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string gotoAccess = Grammar.GetOptionValue("pg.parser.goto.access", "private");
            Identifier gotoName = Identifier.New(Grammar.GetOptionValue("pg.parser.goto.name", "Goto"));
            Identifier stateName = Identifier.New(Grammar.GetOptionValue("pg.parser.state.name", "state"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.parser.c.name", "c"));
            Identifier baseArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.base.name", "_pbase"));
            Identifier checkArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.check.name", "_pcheck"));
            Identifier valueArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.value.name", "_pvalue"));
            writer.Write(gotoAccess);
            if (!gotoAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static int ");
            WriteIdentifier(gotoName, writer);
            writer.Write("(int ");
            WriteIdentifier(stateName, writer);
            writer.Write(", int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("return ");
            WriteIdentifier(valueArrayName, writer);
            writer.Write("[");
            WriteIdentifier(baseArrayName, writer);
            writer.Write("[{0} + ", Automaton.States.Count);
            WriteIdentifier(cName, writer);
            writer.Write("] + ");
            WriteIdentifier(stateName, writer);
            writer.WriteLine("];");
            writer.CloseCurlyBlock();
        }

        public void GenerateMakeErrorMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string makeErrorAccess = Grammar.GetOptionValue("pg.parser.makeerror.access", "private");
            Identifier makeErrorName = Identifier.New(Grammar.GetOptionValue("pg.parser.makeerror.name", "MakeError"));
            Identifier tokensName = Identifier.New(Grammar.GetOptionValue("pg.parser.makeerror.tokens.name", "tokens"));
            Identifier messageName = Identifier.New(Grammar.GetOptionValue("pg.parser.makeerror.message.name", "message"));
            Identifier sbName = Identifier.New(Grammar.GetOptionValue("pg.parser.makeerror.sb.name", "sb"));
            Identifier eName = Identifier.New(Grammar.GetOptionValue("pg.parser.makeerror.e.name", "e"));
            Identifier stateName = Identifier.New(Grammar.GetOptionValue("pg.parser.state.name", "state"));
            Identifier tName = Identifier.New(Grammar.GetOptionValue("pg.parser.t.name", "t"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.parser.c.name", "c"));
            Identifier sName = Identifier.New(Grammar.GetOptionValue("pg.parser.s.name", "s"));
            Identifier tokenizerName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenizer.name", "Tokenizer"));
            Identifier tokenContextName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokencontext.name", "TokenContext"));
            Identifier textArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.text.name", "_text"));
            Identifier baseArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.base.name", "_pbase"));
            Identifier checkArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.check.name", "_pcheck"));
            Identifier valueArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.value.name", "_pvalue"));
            Identifier actionName = Identifier.New(Grammar.GetOptionValue("pg.parser.action.name", "Action"));
            Identifier stringFormatName = Identifier.New(Grammar.GetOptionValue("pg.string.format.name", "Format"));
            Identifier arrayListClassName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.classname", "List"));
            Identifier arrayListAddName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.add.name", "Add"));
            Identifier arrayListClearName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.clear.name", "Clear"));
            Identifier arrayListCountName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.count.name", "Count"));
            string arrayListNamespace = Grammar.GetOptionValue("pg.arraylist.namespace", "System.Collections.Generic");
            Identifier stringBuilderClassName = Identifier.New(Grammar.GetOptionValue("pg.stringbuilder.classname", "StringBuilder"));
            Identifier stringBuilderAppendName = Identifier.New(Grammar.GetOptionValue("pg.stringbuilder.append.name", "Append"));
            Identifier stringBuilderToStringName = Identifier.New(Grammar.GetOptionValue("pg.stringbuilder.tostring.name", "ToString"));
            string stringBuilderNamespace = Grammar.GetOptionValue("pg.stringbuilder.namespace", "System.Text");
            Identifier syntaxErrorExceptionClassName = Identifier.New(Grammar.GetOptionValue("pg.syntaxerrorexception.classname", "SyntaxErrorException"));
            Identifier syntaxErrorExceptionAddLocationName = Identifier.New(Grammar.GetOptionValue("pg.syntaxerrorexception.addlocation.name", "AddLocation"));
            string syntaxErrorExceptionNamespace = Grammar.GetOptionValue("pg.syntaxerrorexception.namespace", "ParserGenerator.Exceptions");
            Identifier unexpectedTokenResourceName = Identifier.New(Grammar.GetOptionValue("pg.sr.UnexpectedToken.name", "UnexpectedToken"));
            Identifier expectFoundResourceName = Identifier.New(Grammar.GetOptionValue("pg.sr.ExpectFound.name", "ExpectFound"));
            string unexpectedTokenResourceText = Grammar.GetOptionValue("pg.sr.UnexpectedToken.text", "Unexpected {0}.");
            string expectFoundResourceText = Grammar.GetOptionValue("pg.sr.ExpectFound.text", "Expect {0}, found {1}.");
            writer.Write(makeErrorAccess);
            if (!makeErrorAccess.IsNullOrEmpty())
                writer.Write(" ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(makeErrorName, writer);
            writer.Write("(int ");
            WriteIdentifier(stateName, writer);
            writer.Write(", int ");
            WriteIdentifier(tName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.Write("<string> ");
            WriteIdentifier(tokensName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(arrayListNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(arrayListClassName, writer);
            writer.WriteLine("<string>();");
            writer.Write("for (int ");
            WriteIdentifier(cName, writer);
            writer.Write(" = 0; ");
            WriteIdentifier(cName, writer);
            writer.Write(" < ");
            WriteIdentifier(textArrayName, writer);
            writer.Write(".Length; ");
            WriteIdentifier(cName, writer);
            writer.WriteLine("++)");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(sName, writer);
            writer.Write(" = ");
            WriteIdentifier(actionName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.Write(", ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(");");
            writer.Write("if ((");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" & 0x1) != 0)");
            writer.OpenBlock();
            WriteIdentifier(tokensName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(textArrayName, writer);
            writer.Write("[");
            WriteIdentifier(cName, writer);
            writer.WriteLine("]);");
            writer.CloseBlock();
            writer.Write("else if ((");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" & 0x2) != 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(tokensName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListClearName, writer);
            writer.WriteLine("();");
            writer.WriteLine("break;");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.Write("string ");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(";");
            writer.Write("if (");
            WriteIdentifier(tokensName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.WriteLine(" == 0)");
            writer.OpenBlock();
            WriteIdentifier(messageName, writer);
            writer.Write(" = string.");
            WriteIdentifier(stringFormatName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(unexpectedTokenResourceName, writer);
            }
            else
            {
                WriteStringLiteral(unexpectedTokenResourceText, writer);
            }
            writer.Write(", ");
            WriteIdentifier(textArrayName, writer);
            writer.Write("[");
            WriteIdentifier(tName, writer);
            writer.WriteLine("]);");
            writer.CloseBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(stringBuilderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(stringBuilderClassName, writer);
            writer.Write(" ");
            WriteIdentifier(sbName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(stringBuilderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(stringBuilderClassName, writer);
            writer.WriteLine("();");
            writer.Write("switch (");
            WriteIdentifier(tokensName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.WriteLine("case 1:");
            writer.OpenBlock();
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[0]);");
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.WriteLine("case 2:");
            writer.OpenBlock();
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[0]);");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.WriteLine("(\" or \");");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[1]);");
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.WriteLine("case 3:");
            writer.OpenBlock();
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[0]);");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.WriteLine("(\", \");");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[1]);");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.WriteLine("(\", or \");");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[2]);");
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.WriteLine("default:");
            writer.OpenBlock();
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[0]);");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.WriteLine("(\", \");");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.WriteLine("[1]);");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.WriteLine("(\", ..., or \");");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderAppendName, writer);
            writer.Write("(");
            WriteIdentifier(tokensName, writer);
            writer.Write("[");
            WriteIdentifier(tokensName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.WriteLine(" - 1]);");
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            WriteIdentifier(messageName, writer);
            writer.Write(" = string.");
            WriteIdentifier(stringFormatName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(expectFoundResourceName, writer);
            }
            else
            {
                WriteStringLiteral(expectFoundResourceText, writer);
            }
            writer.Write(", ");
            WriteIdentifier(sbName, writer);
            writer.Write(".");
            WriteIdentifier(stringBuilderToStringName, writer);
            writer.Write("(), ");
            WriteIdentifier(textArrayName, writer);
            writer.Write("[");
            WriteIdentifier(tName, writer);
            writer.WriteLine("]);");
            writer.CloseCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(eName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write("(");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(");");
            WriteIdentifier(eName, writer);
            writer.Write(".");
            WriteIdentifier(syntaxErrorExceptionAddLocationName, writer);
            writer.Write("(");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(tokenContextName, writer);
            writer.WriteLine(");");
            writer.Write("return ");
            WriteIdentifier(eName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
        }

        public void GenerateReduceMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            WriteReduceMethod(Automaton.Productions, writer);
        }

        public void GenerateParseGoalMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier parseGoalName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.name", "ParseGoal"));
            Identifier reduceName = Identifier.New(Grammar.GetOptionValue("pg.parser.reduce.name", "Reduce"));
            Identifier makeErrorName = Identifier.New(Grammar.GetOptionValue("pg.parser.makeerror.name", "MakeError"));
            Identifier goalName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.goal.name", "goal"));
            Identifier resultName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.result.name", "result"));
            Identifier valueName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.value.name", "value"));
            Identifier stateName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.state.name", "state"));
            Identifier tName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.t.name", "t"));
            Identifier sName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.s.name", "s"));
            Identifier leftName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.left.name", "left"));
            Identifier lengthName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.length.name", "length"));
            Identifier prodName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.s.name", "prod"));
            Identifier statesName = Identifier.New(Grammar.GetOptionValue("pg.parser.states.name", "_states"));
            Identifier symbolsName = Identifier.New(Grammar.GetOptionValue("pg.parser.symbols.name", "_symbols"));
            Identifier valuesName = Identifier.New(Grammar.GetOptionValue("pg.parser.values.name", "_values"));
            Identifier contextsName = Identifier.New(Grammar.GetOptionValue("pg.parser.contexts.name", "_contexts"));
            Identifier tokenizerName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenizer.name", "Tokenizer"));
            Identifier getNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.parser.getnexttoken.name", "GetNextToken"));
            Identifier tokenValueName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokenvalue.name", "TokenValue"));
            Identifier tokenContextName = Identifier.New(Grammar.GetOptionValue("pg.parser.tokencontext.name", "TokenContext"));
            Identifier nodeContextName = Identifier.New(Grammar.GetOptionValue("pg.parser.nodecontext.name", "_nodeContext"));
            Identifier actionName = Identifier.New(Grammar.GetOptionValue("pg.parser.action.name", "Action"));
            Identifier gotoName = Identifier.New(Grammar.GetOptionValue("pg.parser.goto.name", "Goto"));
            Identifier nonterminalsArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.nonterminals.name", "_nonterminals"));
            Identifier tokensArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.tokens.name", "_tokens"));
            Identifier goalArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.goal.name", "_goal"));
            Identifier leftArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.left.name", "_left"));
            Identifier lengthArrayName = Identifier.New(Grammar.GetOptionValue("pg.parser.array.length.name", "_length"));
            Identifier arrayListClassName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.classname", "List"));
            Identifier arrayListAddName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.add.name", "Add"));
            Identifier arrayListRemoveRangeName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.removerange.name", "RemoveRange"));
            Identifier arrayListClearName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.clear.name", "Clear"));
            Identifier arrayListCountName = Identifier.New(Grammar.GetOptionValue("pg.arraylist.count.name", "Count"));
            string arrayListNamespace = Grammar.GetOptionValue("pg.arraylist.namespace", "System.Collections.Generic");
            Identifier sourceContextClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));
            Identifier sourceContextNewName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.new.name", "New"));
            Identifier sourceContextBeginName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.begin.name", "Begin"));
            Identifier sourceContextEndName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.end.name", "End"));
            string sourceContextNamespace = Grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.Input");
            writer.Write("private object ");
            WriteIdentifier(parseGoalName, writer);
            writer.Write("(int ");
            WriteIdentifier(goalName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(stateName, writer);
            writer.Write(" = ");
            WriteIdentifier(goalArrayName, writer);
            writer.Write("[");
            WriteIdentifier(goalName, writer);
            writer.WriteLine("];");
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.WriteLine(");");
            writer.Write("int ");
            WriteIdentifier(tName, writer);
            writer.Write(" = ");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(getNextTokenName, writer);
            writer.WriteLine("();");
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(sName, writer);
            writer.Write(" = ");
            WriteIdentifier(actionName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.Write(", ");
            WriteIdentifier(tName, writer);
            writer.WriteLine(");");
            writer.Write("if ((");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" & 0x1) != 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(stateName, writer);
            writer.Write(" = ");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" >> 4;");
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.WriteLine(");");
            WriteIdentifier(symbolsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(tokensArrayName, writer);
            writer.Write("[");
            WriteIdentifier(tName, writer);
            writer.WriteLine("]);");
            WriteIdentifier(valuesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(tokenValueName, writer);
            writer.WriteLine(");");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(tokenContextName, writer);
            writer.WriteLine(");");
            WriteIdentifier(tName, writer);
            writer.Write(" = ");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(getNextTokenName, writer);
            writer.WriteLine("();");
            writer.CloseCurlyBlock();
            writer.Write("else if ((");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" & 0x2) != 0)");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(prodName, writer);
            writer.Write(" = ");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" >> 4;");
            writer.Write("int ");
            WriteIdentifier(leftName, writer);
            writer.Write(" = ");
            WriteIdentifier(leftArrayName, writer);
            writer.Write("[");
            WriteIdentifier(prodName, writer);
            writer.WriteLine("];");
            writer.Write("int ");
            WriteIdentifier(lengthName, writer);
            writer.Write(" = ");
            WriteIdentifier(lengthArrayName, writer);
            writer.Write("[");
            WriteIdentifier(prodName, writer);
            writer.WriteLine("];");
            writer.Write("if (");
            WriteIdentifier(lengthName, writer);
            writer.WriteLine(" == 0)");
            writer.OpenBlock();
            WriteIdentifier(nodeContextName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextNewName, writer);
            writer.Write("(");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(tokenContextName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextBeginName, writer);
            writer.Write(", ");
            if (!UseEmbeddedTokenizer)
            {
                WriteIdentifier(tokenizerName, writer);
                writer.Write(".");
            }
            WriteIdentifier(tokenContextName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextBeginName, writer);
            writer.WriteLine(");");
            writer.CloseBlock();
            writer.WriteLine("else");
            writer.OpenBlock();
            WriteIdentifier(nodeContextName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextNewName, writer);
            writer.Write("(");
            WriteIdentifier(contextsName, writer);
            writer.Write("[");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - ");
            WriteIdentifier(lengthName, writer);
            writer.Write("].");
            WriteIdentifier(sourceContextBeginName, writer);
            writer.Write(", ");
            WriteIdentifier(contextsName, writer);
            writer.Write("[");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - 1].");
            WriteIdentifier(sourceContextEndName, writer);
            writer.WriteLine(");");
            writer.CloseBlock();
            writer.Write("if ((");
            WriteIdentifier(sName, writer);
            writer.WriteLine(" & 0x4) != 0)");
            writer.OpenCurlyBlock();
            writer.Write("object ");
            WriteIdentifier(resultName, writer);
            writer.Write(" = ");
            WriteIdentifier(valuesName, writer);
            writer.WriteLine("[0];");
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListClearName, writer);
            writer.WriteLine("();");
            WriteIdentifier(symbolsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListClearName, writer);
            writer.WriteLine("();");
            WriteIdentifier(valuesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListClearName, writer);
            writer.WriteLine("();");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListClearName, writer);
            writer.WriteLine("();");
            writer.Write("return ");
            WriteIdentifier(resultName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            writer.Write("object ");
            WriteIdentifier(valueName, writer);
            writer.Write(" = ");
            WriteIdentifier(reduceName, writer);
            writer.Write("(");
            WriteIdentifier(prodName, writer);
            writer.WriteLine(");");
            writer.Write("if (");
            WriteIdentifier(lengthName, writer);
            writer.WriteLine(" > 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListRemoveRangeName, writer);
            writer.Write("(");
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - ");
            WriteIdentifier(lengthName, writer);
            writer.Write(", ");
            WriteIdentifier(lengthName, writer);
            writer.WriteLine(");");
            WriteIdentifier(symbolsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListRemoveRangeName, writer);
            writer.Write("(");
            WriteIdentifier(symbolsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - ");
            WriteIdentifier(lengthName, writer);
            writer.Write(", ");
            WriteIdentifier(lengthName, writer);
            writer.WriteLine(");");
            WriteIdentifier(valuesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListRemoveRangeName, writer);
            writer.Write("(");
            WriteIdentifier(valuesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - ");
            WriteIdentifier(lengthName, writer);
            writer.Write(", ");
            WriteIdentifier(lengthName, writer);
            writer.WriteLine(");");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListRemoveRangeName, writer);
            writer.Write("(");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - ");
            WriteIdentifier(lengthName, writer);
            writer.Write(", ");
            WriteIdentifier(lengthName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            WriteIdentifier(stateName, writer);
            writer.Write(" = ");
            WriteIdentifier(gotoName, writer);
            writer.Write("(");
            WriteIdentifier(statesName, writer);
            writer.Write("[");
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListCountName, writer);
            writer.Write(" - 1], ");
            WriteIdentifier(leftName, writer);
            writer.WriteLine(");");
            WriteIdentifier(statesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.WriteLine(");");
            WriteIdentifier(symbolsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(nonterminalsArrayName, writer);
            writer.Write("[");
            WriteIdentifier(leftName, writer);
            writer.WriteLine("]);");
            WriteIdentifier(valuesName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(valueName, writer);
            writer.WriteLine(");");
            WriteIdentifier(contextsName, writer);
            writer.Write(".");
            WriteIdentifier(arrayListAddName, writer);
            writer.Write("(");
            WriteIdentifier(nodeContextName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            writer.Write("throw ");
            WriteIdentifier(makeErrorName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.Write(", ");
            WriteIdentifier(tName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        private GrammarGoalDescriptor FindDefaultGoal()
        {
            GrammarGoalDescriptor defaultGoal = null;
            foreach (GrammarGoalDescriptor goal in Automaton.Goals.Values)
                if (defaultGoal == null || goal.Name.ToLower().Contains("interactive"))
                    defaultGoal = goal;
            if (defaultGoal == null)
            {
                GrammarErrorException e = new GrammarErrorException(SR.DefaultGoalNotFound);
                throw e;
            }
            return defaultGoal;
        }

        public void GenerateParseMethods(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string parseAccess = Grammar.GetOptionValue("pg.parser.parse.access", "public");
            Identifier parseName = Identifier.New(Grammar.GetOptionValue("pg.parser.parse.name", "Parse"));
            Identifier parseGoalName = Identifier.New(Grammar.GetOptionValue("pg.parser.parsegoal.name", "ParseGoal"));
            GrammarGoalDescriptor defaultGoal = FindDefaultGoal();
            writer.Write(parseAccess);
            if (!parseAccess.IsNullOrEmpty())
                writer.Write(" ");
            if (defaultGoal.GoalSymbol.HasSemanticValue)
                WriteTypeExpression(defaultGoal.GoalSymbol.Type, writer);
            else
                writer.Write("void");
            writer.Write(" ");
            WriteIdentifier(parseName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            if (defaultGoal.GoalSymbol.HasSemanticValue)
                writer.Write("return ");
            Identifier parseGoalMethodName = Identifier.New(parseName.Name + defaultGoal.Name);
            WriteIdentifier(parseGoalMethodName, writer);
            writer.WriteLine("();");
            writer.CloseCurlyBlock();
            foreach (GrammarGoalDescriptor goal in Automaton.Goals.Values)
            {
                writer.WriteLine();
                writer.Write(parseAccess);
                if (!parseAccess.IsNullOrEmpty())
                    writer.Write(" ");
                if (goal.GoalSymbol.HasSemanticValue)
                    WriteTypeExpression(goal.GoalSymbol.Type, writer);
                else
                    writer.Write("void");
                writer.Write(" ");
                Identifier parseMethodName = Identifier.New(parseName.Name + goal.Name);
                WriteIdentifier(parseMethodName, writer);
                writer.WriteLine("()");
                writer.OpenCurlyBlock();
                if (goal.GoalSymbol.HasSemanticValue)
                {
                    writer.Write("return (");
                    WriteTypeExpression(goal.GoalSymbol.Type, writer);
                    writer.Write(")");
                }
                WriteIdentifier(parseGoalName, writer);
                writer.WriteLine("({0});", goal.Number);
                writer.CloseCurlyBlock();
            }
        }

        public void GenerateTestMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier parserClassName = Identifier.New(Grammar.GetOptionValue("pg.parser.classname", "Parser"));
            string parserNamespace = Grammar.GetOptionValue("pg.parser.namespace", "ParserGenerator.SyntacticAnalysis");
            if (UseEmbeddedTokenizer)
            {
                Identifier testTokenizerName = Identifier.New(Grammar.GetOptionValue("pg.parser.testtokenizer.name", "TestTokenizer"));
                ScannerGenerator.GenerateTestMethod(writer, testTokenizerName, parserClassName, parserNamespace);
                writer.WriteLine();
            }
            string testAccess = Grammar.GetOptionValue("pg.parser.test.access", "public");
            Identifier testName = Identifier.New(Grammar.GetOptionValue("pg.parser.test.name", "Test"));
            Identifier testParserName = Identifier.New(Grammar.GetOptionValue("pg.parser.test.parser.name", "parser"));
            Identifier testEName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.e.name", "e"));
            Identifier testAstName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.ast.name", "ast"));
            Identifier testAstToStringName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.asttostring.name", "ToString"));
            Identifier stringIsNullOrWhiteSpaceName = Identifier.New(Grammar.GetOptionValue("pg.string.isnullorwhitespace.name", "IsNullOrWhiteSpace"));
            Identifier testStrName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.str.name", "str"));
            Identifier parseName = Identifier.New(Grammar.GetOptionValue("pg.parser.parse.name", "Parse"));
            Identifier exceptionExtensionsClassName = Identifier.New(Grammar.GetOptionValue("pg.exceptionextensions.classname", "ExceptionExtensions"));
            string exceptionExtensionsNamespace = Grammar.GetOptionValue("pg.exceptionextensions.namespace", "ParserGenerator.Extensions");
            Identifier exceptionExtensionsPrintName = Identifier.New(Grammar.GetOptionValue("pg.exceptionextensions.print.name", "Print"));
            Identifier sourceReaderClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.classname", "SourceReader"));
            string sourceReaderNamespace = Grammar.GetOptionValue("pg.sourcereader.namespace", "ParserGenerator.Input");
            Identifier sourceReaderFromConsoleName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.fromconsole.name", "FromConsole"));
            Identifier readerPeekName = Identifier.New(Grammar.GetOptionValue("pg.reader.peek.name", "Peek"));
            Identifier testReaderName = Identifier.New(Grammar.GetOptionValue("pg.parser.test.reader.name", "reader"));
            writer.Write(testAccess);
            if (!testAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static void ");
            WriteIdentifier(testName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(" ");
            WriteIdentifier(testReaderName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceReaderFromConsoleName, writer);
            writer.WriteLine("();");
            writer.Write("if (");
            WriteIdentifier(testReaderName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == -1)");
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.WriteLine("try");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(parserNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(parserClassName, writer);
            writer.Write(" ");
            WriteIdentifier(testParserName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(parserNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(parserClassName, writer);
            writer.Write("(");
            WriteIdentifier(testReaderName, writer);
            writer.WriteLine(");");
            GrammarGoalDescriptor defaultGoal = FindDefaultGoal();
            if (defaultGoal.GoalSymbol.HasSemanticValue)
            {
                WriteTypeExpression(defaultGoal.GoalSymbol.Type, writer);
                writer.Write(" ");
                WriteIdentifier(testAstName, writer);
                writer.Write(" = ");
                WriteIdentifier(testParserName, writer);
                writer.Write(".");
                WriteIdentifier(parseName, writer);
                writer.WriteLine("();");
                writer.Write("string ");
                WriteIdentifier(testStrName, writer);
                writer.Write(" = ");
                WriteIdentifier(testAstName, writer);
                writer.Write(".");
                WriteIdentifier(testAstToStringName, writer);
                writer.WriteLine("();");
                writer.Write("if (!string.");
                WriteIdentifier(stringIsNullOrWhiteSpaceName, writer);
                writer.Write("(");
                WriteIdentifier(testStrName, writer);
                writer.WriteLine("))");
                writer.OpenBlock();
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.Write("Console.WriteLine(");
                WriteIdentifier(testStrName, writer);
                writer.WriteLine(");");
                writer.CloseBlock();
            }
            else
            {
                WriteIdentifier(testParserName, writer);
                writer.Write(".");
                WriteIdentifier(parseName, writer);
                writer.WriteLine("();");
            }
            writer.CloseCurlyBlock();
            writer.Write("catch (");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("Exception ");
            WriteIdentifier(testEName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(exceptionExtensionsNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(exceptionExtensionsClassName, writer);
            writer.Write(".");
            WriteIdentifier(exceptionExtensionsPrintName, writer);
            writer.Write("(");
            WriteIdentifier(testEName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }
    }
}
